/**
 * @file STL_DnsCache.h
 * @author Derek Liu (yjkhtddx@sina.com)
 * @brief 
 * @version 2.00.05
 * @date 2022-03-02
 * 
 * @copyright Copyright Julong (c) 2022
 * 
 */

#ifndef __STL_DNS_CACHE_H__
#define __STL_DNS_CACHE_H__

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string>
#include <mutex>
#include <STLOG.h>
#include <unordered_map>

using namespace std;

namespace STL
{
    class STL_DnsCache
    {
    public:
        static STL_DnsCache &getInstance()
        {
            static STL_DnsCache instance;
            return instance;
        }
        std::string getDomainIP(std::string host, int expireSec = 60)
        {
            sockaddr addr;
            auto flag = getDomainIP(host.c_str(), addr, expireSec);
            if (flag)
            {
                return std::string(inet_ntoa(((sockaddr_in *)(&addr))->sin_addr));
            }
            return "";
        };
        bool getDomainIP(const char *host, sockaddr &addr, int expireSec = 60)
        {
            DnsItem item;
            auto flag = getCacheDomainIP(host, item, expireSec);
            if (!flag)
            {
                flag = getSystemDomainIP(host, item._addr);
                if (flag)
                {
                    setCacheDomainIP(host, item);
                }
            }
            if (flag)
            {
                addr = item._addr;
            }
            return flag;
        }

    private:
        STL_DnsCache() = default;
        ~STL_DnsCache() = default;

        class DnsItem
        {
        public:
            sockaddr _addr;
            time_t _create_time;
        };

        bool getCacheDomainIP(const char *host, DnsItem &item, int expireSec)
        {
            lock_guard<mutex> lck(_mtx);
            auto it = _mapDns.find(host);
            if (it == _mapDns.end())
            {
                //没有记录
                return false;
            }
            if (it->second._create_time + expireSec < time(NULL))
            {
                //已过期
                _mapDns.erase(it);
                return false;
            }
            item = it->second;
            return true;
        }

        void setCacheDomainIP(const char *host, DnsItem &item)
        {
            lock_guard<mutex> lck(_mtx);
            item._create_time = time(NULL);
            _mapDns[host] = item;
        }

        bool getSystemDomainIP(const char *host, sockaddr &item)
        {
            struct addrinfo *answer = nullptr;
            //阻塞式dns解析，可能被打断
            int ret = -1;
            int temp_errno;
            do
            {
                ret = getaddrinfo(host, NULL, NULL, &answer);
                temp_errno = errno;

            } while (ret == -1 && temp_errno == EAGAIN);

            if (!answer)
            {
                stlog_warn("域名解析失败:%s", host);
                return false;
            }
            item = *(answer->ai_addr);
            freeaddrinfo(answer);
            return true;
        }

    private:
        mutex _mtx;
        unordered_map<string, DnsItem> _mapDns;
    };
}
#endif // __STL_DNS_CACHE_H__